package com.xwj.action.impl;

import com.alibaba.fastjson.JSONObject;
import com.xwj.action.OrderTccAction;
import com.xwj.dao.OrderMapper;
import com.xwj.dao.OrderTxLogMapper;
import com.xwj.domain.Order;
import com.xwj.domain.OrderTxLog;
import com.xwj.enums.TccTransactionalStatus;
import io.seata.rm.tcc.api.BusinessActionContext;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallback;
import org.springframework.transaction.support.TransactionTemplate;

import java.util.Date;

/**
 * @author xwj
 * @date 2020/11/13
 */
@Component
@Slf4j
public class OrderTccActionImpl implements OrderTccAction {
    @Autowired
    private OrderMapper orderMapper;
    @Autowired
    private TransactionTemplate transactionTemplate;
    @Autowired
    private OrderTxLogMapper orderTxLogMapper;

    /**
     * Try 方法主要需要考虑两个问题：
     * 一个是 Try 方法需要能够告诉二阶段接口，已经预留业务资源成功。
     * 第二个是需要检查第二阶段是否已经执行完成，如果已完成，则不再执行
     *
     * @param businessActionContext
     * @param order
     * @return
     */
    @Override
    public boolean createOrderTccAction(BusinessActionContext businessActionContext, final Order order) {
        // 分布式事务ID
        String xid = businessActionContext.getXid();
        long branchId = businessActionContext.getBranchId();
        return transactionTemplate.execute(new TransactionCallback<Boolean>() {
            @Override
            public Boolean doInTransaction(TransactionStatus status) {
                log.info("=========xid：{}，branchId：{}，执行Try操作", xid, branchId);
                try {
                    // 插入事务控制表记录，如果插入成功，说明第二阶段还没有执行，可以继续执行第一阶段。
                    // 如果插入失败，则说明第二阶段已经执行或正在执行，则抛出异常，终止即可
                    OrderTxLog orderTxLog = new OrderTxLog(xid, branchId, TccTransactionalStatus.TRY.getCode(), new Date(), new Date());
                    int result = orderTxLogMapper.insert(orderTxLog);
                    if (result != 1) {
                        return false;
                    }
                    // 执行业务一阶段：创建订单
                    orderMapper.create(order);
                    return true;
                } catch (Exception e) {
                    e.printStackTrace();
                    status.setRollbackOnly();
                    return false;
                }
            }
        });
    }

    /**
     * 因为 Confirm 方法不允许空回滚，也就是说，Confirm 方法一定要在 Try 方法之后执行。
     * 因此，Confirm 方法只需要关注重复提交的问题。
     *
     * @param businessActionContext
     * @return
     */
    @Override
    public boolean commitCreateOrder(BusinessActionContext businessActionContext) {
        // 分布式事务ID
        String xid = businessActionContext.getXid();
        long branchId = businessActionContext.getBranchId();
        return transactionTemplate.execute(new TransactionCallback<Boolean>() {
            @Override
            public Boolean doInTransaction(TransactionStatus status) {
                log.info("=========xid：{}，branchId：{}，执行Commit操作", xid, branchId);
                try {
                    // 查询事务记录，先锁定事务记录，根据唯一索引xid、branch_id进行查询，产生行锁
                    OrderTxLog orderTxLog = orderTxLogMapper.selectOne(xid, branchId);
                    if (orderTxLog == null) {
                        // 如果事务记录为空，则说明是一个空提交，不允许，终止执行
                        return false;
                    }
                    // 如果事务记录不为空，则继续检查状态
                    if (orderTxLog.getStatus().equals(TccTransactionalStatus.CONFIRM.getCode())) {
                        // 如果状态是已提交，则认为是重复提交，直接返回成功即可
                        return true;
                    }
                    if (orderTxLog.getStatus().equals(TccTransactionalStatus.CANCEL.getCode())) {
                        // 如果状态是已回滚，也是一个异常，一个已回滚的事务，不能重新提交，需要能够拦截到这种异常情况，并报警
                        throw new RuntimeException("请勿重复创建订单");
                    }
                    JSONObject order = (JSONObject) businessActionContext.getActionContext("order");
                    // 修改事务日志状态为已提交
                    int result = orderTxLogMapper.updateStatus(orderTxLog.getXid(), orderTxLog.getBranchId(), TccTransactionalStatus.TRY.getCode(), TccTransactionalStatus.CONFIRM.getCode());
                    if (result == 1) {
                        // 修改订单状态
                        orderMapper.updateStatus(order.getLong("id"), order.getInteger("status"), 1);
                        return true;
                    }
                    return false;
                } catch (Exception e) {
                    e.printStackTrace();
                    status.setRollbackOnly();
                    return false;
                }
            }
        });
    }

    /**
     * 因为 Cancel 方法允许空回滚，并且要在先执行的情况下，让 Try 方法感知到 Cancel 已经执行，所以和 Confirm 方法略有不同。
     *
     * @param businessActionContext
     * @return
     */
    @Override
    public boolean rollbackCreateOrder(BusinessActionContext businessActionContext) {
        // 分布式事务ID
        String xid = businessActionContext.getXid();
        long branchId = businessActionContext.getBranchId();
        return transactionTemplate.execute(new TransactionCallback<Boolean>() {
            @Override
            public Boolean doInTransaction(TransactionStatus status) {
                log.info("=========xid：{}，branchId：{}，执行Rollback操作", xid, branchId);
                try {
                    // 查询事务记录，锁定事务记录，根据唯一索引xid、branch_id进行查询，产生行锁
                    OrderTxLog orderTxLog = orderTxLogMapper.selectOne(xid, branchId);
                    // 如果事务记录为空，则认为 Try 方法还没执行，即是空回滚
                    if (orderTxLog == null) {
                        // 允许空回滚，应该先插入一条事务记录，确保后续的 Try 方法不会再执行
                        int result = orderTxLogMapper.insert(new OrderTxLog(xid, branchId, TccTransactionalStatus.CANCEL.getCode(), new Date(), new Date()));
                        if (result != 1) {
                            // 插入失败，则认为 Try 方法正再执行，等待 TC 的重试即可
                            return true;
                        }
                        // 插入成功，则说明 Try 方法还没有执行，空回滚继续执行
                    } else {
                        // 事务记录不为空，则说明 Try 方法已经执行完毕
                        if (orderTxLog.getStatus().equals(TccTransactionalStatus.CONFIRM.getCode())) {
                            // 如果状态为已提交，则同样是一个异常，一个已提交的事务，不能再次回滚
                            throw new RuntimeException("事务已经提交，请勿回滚");
                        }
                        if (orderTxLog.getStatus().equals(TccTransactionalStatus.CANCEL.getCode())) {
                            // 如果状态为已回滚，则说明这是重复调用，允许幂等，直接返回成功即可
                            return true;
                        }
                        // 正常执行 Cancel 逻辑
                        // 修改事务日志状态为已回滚
                        if (orderTxLogMapper.updateStatus(xid, branchId, TccTransactionalStatus.TRY.getCode(), TccTransactionalStatus.CANCEL.getCode()) == 1) {
                            JSONObject order = (JSONObject) businessActionContext.getActionContext("order");
                            orderMapper.delete(order.getLong("userId"), order.getLong("productId"));
                            return true;
                        } else {
                            return false;
                        }
                    }
                    return true;
                } catch (Exception e) {
                    e.printStackTrace();
                    status.setRollbackOnly();
                    return false;
                }
            }
        });
    }
}
